//
// $Id: SampleManager.java,v 1.21 2004/08/27 18:51:26 mdb Exp $

package com.samskievert.cactusboom;

import com.threerings.crowd.data.PlaceObject;
import com.threerings.presents.dobj.DSet;

import com.threerings.parlor.game.server.GameManager;
import com.threerings.util.Name;

import com.threerings.toybox.data.ToyBoxGameConfig;

import com.threerings.crowd.data.BodyObject;

import com.threerings.parlor.turn.server.TurnGameManager;
import com.threerings.parlor.turn.server.TurnGameManagerDelegate;

import java.lang.String;
import java.lang.Integer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Random;



/**
 * Handles the server side of the game.
 */
public class CBManager extends GameManager
        implements TurnGameManager
{
    
   @Override // from GameManager
   protected void assignWinners (boolean[] winners)
   {
       super.assignWinners(winners);
 
       //for now, EVERYBODY WINS!
       for(int i = 0; i < winners.length; i++)
       {
        winners[i] = true;   
       }
   }

    @Override // from PlaceManager
    public void didInit ()
    {
        super.didInit();

        // get a casted reference to our game configuration
        _gameconf = (ToyBoxGameConfig)_config;

        // create our game logic instance
       _logic = new CBLogic(8); // TODO: get board size from config


        // this is called when our manager is created but before any
        // game-specific actions take place; we don't yet have our game object
        // at this point but we do have our game configuration
    }

   /** Used to determine legality of moves, etc. */
   protected CBLogic _logic;


    @Override // from PlaceManager
    public void didStartup ()
    {
        super.didStartup();
        
        ToyBoxGameConfig config = (ToyBoxGameConfig)_config;
        String i = (String)config.params.get("num_rounds");
        if(!i.equals("infinite"))  maxRounds = Integer.valueOf(i).intValue();
        //for testing, maxRouns = 2 is muuuuch quicker
        //maxRounds = 2;
        
        // grab our own casted game object reference
        _gameobj = (CBObject)super._gameobj;
 
        _gameobj.setMaxRounds(maxRounds);
        //this was a configurable param, but didn't have time to implement
        //puzzle editor for the 8-spiked cacti
        _gameobj.setNUMSPIKES(4);        
        
        // this method is called after we have created our game object but
        // before we do any game related things
    }

    @Override // from PlaceManager
    public void didShutdown ()
    {
        super.didShutdown();

        // this is called right before we finally disappear for good
    }

    @Override // from PlaceManager
    protected PlaceObject createPlaceObject ()
    {
        return new CBObject();
    }

    @Override // from GameManager
    protected void gameWillStart ()
    {
        super.gameWillStart();

        // when all the players have entered the game room, the game is
        // automatically started and this method is called just before the
        // event is delivered to the clients that will start the game

        // this is the place to do any pre-game setup that needs to be done
        // each time a game is started rather than just once at the very
        // beginning (those sorts of things should be done in didStartup())
        // start the game with the standard arrangement of pieces
       
       Name[] test = _gameobj.getPlayers();
       
       int[] tp = new int[test.length];
       //tp[0] = 10000;
       _gameobj.setTurnPoints(tp);
       int[] mp = new int[test.length];
       _gameobj.setMovePoints(mp);
       
       int[] pe = new int[test.length];
       _gameobj.setPlayerExperience(pe);
       int[] pl = new int[test.length];
       _gameobj.setPlayerLevel(pl);
       
       int[] e = new int[test.length];
       _gameobj.setExplosions(e);
       
       int[] ll = new int[test.length];
       
       _gameobj.setLivesLeft(ll);
       
       //the default positions are top left, bottom right, top right, and bottom left
       int[] startX = {0,7,7,0};
       int[] startY = {0,7,0,7};
       
       for(int i = 0; i < test.length; i++){
           CBObject.Cowboy cowboy = new CBObject.Cowboy();
           cowboy.pieceId = i;
           cowboy.owner = i;
           cowboy.x = startX[i]; cowboy.y = startY[i];
           _gameobj.addToCowboys(cowboy);
       }
       
       int[] claimCount = new int[test.length];
       for(int i = 0; i < 64; i += test.length){
           for(int j = 0; j < test.length; j++){
               if(i+j < 64) claimCount[j]++;
           }
       }
       
       //this should call DIMENSION, but hardcoded for now
       //go ahead and create a whole bunch of empty claims
       for(int i = 0; i < 8; i++){
           for(int j = 0; j < 8; j++){
               CBObject.Claim claim = new CBObject.Claim();  
               boolean gotOne = false;
               int numTries = 50;
               Random rand = new Random();
               int whoseClaim = -1; //unclaimed, won't cause an error
               while(!gotOne && numTries > 0){
                   numTries--;
                   int r = rand.nextInt(claimCount.length);
                   if(claimCount[r] > 0)
                   {
                       claimCount[r]--;
                       whoseClaim = r;
                       gotOne = true;
                   }
               }
               claim.owner = whoseClaim;
               claim.strength = 10;
               claim.pieceId = i*8 + j;
               claim.x = i;
               claim.y = j;
               _gameobj.addToClaims(claim);
           }
       }
       
       //now add the home territory pieces
       for(int i = 0; i < test.length; i++){
           
           CBObject.Claim claim = (CBObject.Claim)_gameobj.claims.get(startX[i] * 8 + startY[i]);
           claim.owner = i;
           claim.strength = 1;
           _gameobj.updateClaims(claim);
       }
          
       
       
       
       int STARTINGGOLD = 2;  //this should really be stuck somewhere else...
       Random r = new Random();
       _logic.setState(_gameobj);
       //_logic.setTerritoryState(_gameobj);
       for(int i = 0; i < STARTINGGOLD; i++){
           boolean gotOne = false;
           int numTries = 0;       
           while(!gotOne && numTries < 1000)
           {
                //should be DIMENSION!
                int tx = r.nextInt(8);
                int ty = r.nextInt(8);
                if(_logic.getColor(tx,ty) == -1){
                    CBObject.Pickup newGold = new CBObject.Pickup();
                    newGold.x = tx; newGold.y = ty;
                    newGold.type = newGold.GOLD;
                    _gameobj.placePickup(newGold);
                    gotOne = true;
                }
           }
       }
      
       //reset the points back to 0
      _gameobj.setTurnPoints(tp);
       
    }
    
    @Override // from GameManager
    protected void gameDidEnd ()
    {
        super.gameDidEnd();

        // this is called after the game has ended. somewhere in the game
        // manager a call to endGame() should be made when the manager knows
        // the game to be over and that will trigger the end-of-game processing
        // including calling this method
    }

   /**
   * Called when a client sends a request to place a piece on the board.
   */
   public void placeCactus (BodyObject player, CBObject.Cactus cactus)
   {
       if(System.currentTimeMillis() - _lastUpdate > 250){
           //System.out.println("Okay, " + (System.currentTimeMillis() - _lastUpdate) + " elapsed.");
           _lastUpdate = System.currentTimeMillis();
       // update our logic with the current state of the board
       _logic.setState(_gameobj);
 
       // make sure it's this player's turn
       int pidx = _turndel.getTurnHolderIndex();
       if (_playerOids[pidx] != player.getOid()) {
           System.err.println("Requested to place cactus by non-turn holder " +
                              "[who=" + player.who() +
                              ", turnHolder=" + _gameobj.turnHolder + "].");
 
       // make sure this is a legal move
       } else if (_logic.isLegalMove(cactus,1)) {
           // place this piece on the board
           _gameobj.placeCactus(cactus);
           
           //if the player had another move, do nothing.  if the player has no
           //legal moves, pass it on
           
           _logic.setState(_gameobj);
           if(_logic.hasLegalMoves(cactus.owner)){
               System.out.println("Not done yet!  " + _gameobj.turnPoints[cactus.owner]);
           }else{
               //next!
              // _turndel.endTurn();
           }
 
       } else {
           System.err.println("Received illegal move request " +
                              "[who=" + player.who() +
                              ", cactus=" + cactus + "].");
       }
     }else{
      System.out.println("Nope, " + (System.currentTimeMillis() - _lastUpdate) + " elapsed.");     
     }

   }
   
   /** Add a spike to a cactus
    *
    */
   public void addSpike (BodyObject player, CBObject.Cactus cactus)
   {
    if(System.currentTimeMillis() - _lastUpdate > 250){
        System.out.println("Okay, " + (System.currentTimeMillis() - _lastUpdate) + " elapsed.");     
        _lastUpdate = System.currentTimeMillis();
       _logic.setState(_gameobj);
 
       // make sure it's this player's turn
       int pidx = _turndel.getTurnHolderIndex();
       if (_playerOids[pidx] != player.getOid()) {
           System.err.println("Requested to place cactus by non-turn holder " +
                              "[who=" + player.who() +
                              ", turnHolder=" + _gameobj.turnHolder + "].");
 
       // make sure this is a legal move
       } else if (_logic.isLegalMove(cactus,1)) {
           cactus.addSpike(cactus.owner);
           _gameobj.updateCactus(cactus, true);
           _gameobj.setNumClicks(_gameobj.numClicks + 1);
           
           _logic.setState(_gameobj);

           if(!_logic.hasLegalMoves(cactus.owner)){
               //_turndel.endTurn();
           }
           
           
       } else {
           System.err.println("Received illegal move request " +
                              "[who=" + player.who() +
                              ", cactus=" + cactus + "].");
       }
    }else{
     System.out.println("Nope, " + (System.currentTimeMillis() - _lastUpdate) + " elapsed.");        
    }

   }
   
   public void moveCowboy(BodyObject player, int whichDir)
   {
    //System.out.println("Move received.");
    // make sure it's this player's turn
    int pidx = _turndel.getTurnHolderIndex();
    if (_playerOids[pidx] != player.getOid()) {
       System.err.println("Requested to move cowboy by non-turn holder " +
                          "[who=" + player.who() +
                          ", turnHolder=" + _gameobj.turnHolder + "].");
       }else{
       //System.out.println("Legal move... looking for " + player.getOid());
       if(_gameobj.cowboys.containsKey(_turndel.getTurnHolderIndex())){
           //System.out.println("Cowboy found...");
           CBObject.Cowboy cowboy = _gameobj.cowboys.get(_turndel.getTurnHolderIndex());
           
           cowboy.mMove(whichDir);
           _gameobj.updateCowboyMove(cowboy);
           
        }
       
         _logic.setState(_gameobj);
         if(_logic.hasLegalMoves(_turndel.getTurnHolderIndex())){
             //System.out.println("Not done yet!  " + _gameobj.turnPoints[_turndel.getTurnHolderIndex()]);
         }else{

             _logic.setState(_gameobj);

             //next!
             //_turndel.endTurn();
         }
       }
   }
   
   public void youGotTheGold(BodyObject player, CBObject.Pickup pickup)
   {
       //System.out.println("Player " + _turndel.getTurnHolderIndex() + " just got some loot!");
       int pidx = _turndel.getTurnHolderIndex();
       if (_playerOids[pidx] == player.getOid()) {
       
            _gameobj.removeFromPickups(pickup.getKey()); 
            
            int newXp = _gameobj.playerExperience[_turndel.getTurnHolderIndex()] + 1;
            if(newXp == _gameobj.playerLevel[_turndel.getTurnHolderIndex()] + 1){
                //level up!
                _gameobj.setPlayerExperienceAt(0,_turndel.getTurnHolderIndex());
                _gameobj.setPlayerLevelAt(1 + _gameobj.playerLevel[_turndel.getTurnHolderIndex()], _turndel.getTurnHolderIndex());
            }else{
                //just update the new experience
                _gameobj.setPlayerExperienceAt(newXp,_turndel.getTurnHolderIndex());
            }
            
            //_gameobj.setTurnPointsAt(_gameobj.turnPoints[_turndel.getTurnHolderIndex()] + 5, _turndel.getTurnHolderIndex());
            
            _logic.setState(_gameobj);
            //_logic.setTerritoryState(_gameobj);
            boolean gotOne = false;
            int numTries = 0;
            Random r = new Random();
            while(!gotOne && numTries < 1000)
            {
                //should be DIMENSION!
                int tx = r.nextInt(8);
                int ty = r.nextInt(8);
                if(_logic.getColor(tx,ty) == -1){
                    CBObject.Pickup newGold = new CBObject.Pickup();
                    newGold.x = tx; newGold.y = ty;
                    newGold.type = newGold.GOLD;
                    _gameobj.placePickup(newGold);
                    gotOne = true;
                }
           }
       }
       
   }
   

   /**
    *This one is used for normal spike-adding (any player).
    */
   
   public void doUpdate(BodyObject player, CBObject.Cactus cactus, boolean dockPoints)
   {
    _gameobj.updateCactus(cactus, dockPoints);     
   }
   
   /**
    *This one only accepts updates from the acting player to prevent
    *every player adding spikes on SpikeSprite impact.
    */
   
   public void doCollisionUpdate(BodyObject player, CBObject.Cactus cactus, boolean dockPoints, int spikesLeft)
   {
    int pidx = _turndel.getTurnHolderIndex();
    if (_playerOids[pidx] == player.getOid()) {
        _gameobj.updateCactus(cactus, dockPoints); 
    }
    //if(spikesLeft == 4) checkTurnEnd();
    else System.out.println(spikesLeft + " spikes left.");
   }
   
   /**
    * Like the above function, this one only accepts updates form the acting player
    * to prevent duplicates.  This records a hit on a player.
    */
   public void spikeHitCowboy(BodyObject player, CBObject.Cowboy cowboy)
   {
    int pidx = _turndel.getTurnHolderIndex();
    if (_playerOids[pidx] == player.getOid()) {
        // This will eventually be game-type dependent.
        // Boom!  - add one to turnPoints
        // Gather - send home
        // Elim   - subtract a life from livesLeft
        _gameobj.setLivesLeftAt(_gameobj.livesLeft[cowboy.owner] + 1,cowboy.owner);
    }
   }
   
   public void gameOn(BodyObject player, DSet<CBObject.Cactus> d, int c)
   {
       int pidx = _turndel.getTurnHolderIndex();
       if (_playerOids[pidx] == player.getOid() && _gameobj.localsOnly) {
        _gameobj.setPieces(d);
        _gameobj.setLocalsOnly(false);
        checkTurnEnd();
       System.out.println("Call to gameOn finished.  localsOnly is " + _gameobj.localsOnly);        
       }else if(_playerOids[pidx] == player.getOid() && !_gameobj.localsOnly) {
           //this gets called when there are no spikes left, so we still want to run it
           //even if localsOnly is false
         checkTurnEnd();           
       }
   }
   

   public CBManager ()
     {
       
         // we're a turn based game, so we use a turn game manager delegate
         addDelegate(_turndel = new TurnGameManagerDelegate(this) {
             //i just want to setNextTurns normally, so I don't need to overwrite this
            protected void setNextTurnHolder () {
               _logic.setState(_gameobj);
               //_logic.setTerritoryState(_gameobj);
               _turnIdx = _logic.getNextTurnHolderIndex(_turnIdx,_gameobj);
             }
            public void setFirstTurnHolder(){
            _turnIdx = 0;
            }
         });
     }

     
     // from interface TurnGameManager
     public void turnWillStart ()
     {
         //looks to see if this is the first turn, then sets
         //the whoWentFirst variable to that player's index
         if(_turndel.getTurnHolderIndex() == -1){
            _gameobj.setWhoWentFirst(_logic.getNextTurnHolderIndex(_turndel.getTurnHolderIndex(), _gameobj));
         }
         System.out.println(_turndel.getTurnHolderIndex());
         //at this point the turn still belongs to the last player
         

        // I eventually want the player to have to roll a dice to see
        // how many points they get this turn.  For now, though, everybody gets
        // a fixed number of points per turn.
         
        Random r = new Random();
        int diceroll = r.nextInt(6) + 1;
        _gameobj.movePoints[_logic.getNextTurnHolderIndex(_turndel.getTurnHolderIndex(),_gameobj)] += diceroll;
        
        //update the dObj
        _gameobj.setMovePointsAt(_gameobj.movePoints[_logic.getNextTurnHolderIndex(_turndel.getTurnHolderIndex(),_gameobj)], _logic.getNextTurnHolderIndex(_turndel.getTurnHolderIndex(),_gameobj)); 
        _gameobj.setNumClicks(0);
        
     }
 
     // from interface TurnGameManager
     public void turnDidStart ()
     {
         if(_turndel.getTurnHolderIndex() == _gameobj.whoWentFirst){
             roundDidStart();
         }
        
        //resetBoard(
        //newBoard(_turndel.getTurnHolderIndex());
        //System.out.println("Board reset for player " + _turndel.getTurnHolderIndex());
        
     }
 
     // from interface TurnGameManager
     public void turnDidEnd ()
     {
      
       System.out.println("Player " + _turndel.getTurnHolderIndex() + " took " + _gameobj.numClicks + " clicks!");
       CBObject.Cowboy cowboy = (CBObject.Cowboy)_gameobj.cowboys.get(_turndel.getTurnHolderIndex());
       CBObject.Claim claim = (CBObject.Claim)_gameobj.claims.get(cowboy.x*8 + cowboy.y);
       if(_gameobj.numClicks <= claim.strength){
           claim.owner = _turndel.getTurnHolderIndex();
           claim.strength = _gameobj.numClicks;
           _gameobj.updateClaims(claim);
       }
         
         
       if(_logic.getNextTurnHolderIndex(_turndel.getTurnHolderIndex(), _gameobj) == _gameobj.whoWentFirst){
           roundDidEnd();
       }
       
     }
     
     public void checkTurnEnd(BodyObject player)
     {
         checkTurnEnd();           
     }
     public void checkTurnEnd(){
        //System.out.println("Checking to see if there are any legal moves...");
         
         //this whole thing needs reworking
        _logic.setState(_gameobj);        
        if(_logic.hasLegalMoves(_turndel.getTurnHolderIndex())){
          //do nothing, you can still move
          //System.out.println("Legal moves left!");
        }else{
           //next!
           //System.out.println("No more moves!");
           if(_gameobj.numClicks > 0) _turndel.endTurn();
        }
    }
     
     public void passTurn(BodyObject player){
        int pidx = _turndel.getTurnHolderIndex();
        if (_playerOids[pidx] == player.getOid()) {
            if(_gameobj.turnPoints[_turndel.getTurnHolderIndex()] < 5){
                _gameobj.setMovePointsAt(0,_turndel.getTurnHolderIndex());
                //_turndel.endTurn();
            }else{
                System.out.println("You must place a cactus!");
            }            
        }
    }
     
     public void letsBegin(BodyObject player){
       // _gameobj.setTurnPointsAt(5,_turndel.getTurnHolderIndex());
     }
     
     public void clearBoard(BodyObject player){
        ArrayList<CBObject.Cactus> deathList = new ArrayList<CBObject.Cactus>();        
        
        for (CBObject.Cactus cactus : _gameobj.pieces) 
        {
            deathList.add(cactus);
        }
        
        for (CBObject.Cactus cactus : deathList) {
            _gameobj.removeFromPieces(cactus.getKey());
        }
     }
     public void newBoard(BodyObject player, int whichPlayer){
         newBoard(whichPlayer);
     }
     
         public void resetBoard(BodyObject player, int color)
    {
     resetBoard(color);
    }
    
    public void resetBoard(int whichPlayer){
     System.out.println("Reset board command received...");
     
    }


    public void newBoard(int player){
       DSet<CBObject.Cactus> newP = new DSet<CBObject.Cactus>();
       _gameobj.setPieces(newP);
       if(player >=0 ){
           int diff = _logic.getDifficulty(_gameobj,_turndel.getTurnHolderIndex());
           int[] STARTERS = _boardgen.getBoard(diff);
           for(int i = 0; i < STARTERS.length; i++){

           }
           
           for(int i = 0; i < STARTERS.length; i++){
               int tx = i % 8;
               int ty = i / 8;
               CBObject.Cactus cactus = new CBObject.Cactus(numSpikes);
               cactus.x = tx;
               cactus.y = ty;
               cactus.owner = player;
               if(STARTERS[i] >=0){
               for(int j = 0; j < STARTERS[i]; j++){
                   cactus.addSpike(cactus.owner);
                   }
                _gameobj.placeCactus(cactus);
                }
           }
        }
    }
     
     
     //this function is called when player 1's turn starts
     public void roundDidStart()
     {
         _gameobj.setWhichRound(_gameobj.whichRound + 1);
     }
     
     //this function is called after every player has moved in a round
     //(useful for item placement, etc...)
     public void roundDidEnd()
     {
         System.out.println("roundDidEnd called");
         if(_gameobj.whichRound >= maxRounds){
             System.out.println("GAME OVER MAN!");
             int hCount = 0;
             int[] numTerrs = new int[_gameobj.getPlayers().length];
             boolean[] winners = new boolean[numTerrs.length];
             for(CBObject.Claim claim : _gameobj.claims){
                if(claim.owner >= 0) {
                    numTerrs[claim.owner]++;
                    if(numTerrs[claim.owner] > hCount) hCount = numTerrs[claim.owner];
                }
             }

             for(int i = 0; i < numTerrs.length; i++){
                 if(numTerrs[i] == hCount) {
                     winners[i] = true;
                    //slightly hacky, but time ran out
                    _gameobj.setBigWinner(i);
                 }
             }
             
             assignWinners(winners);
             endGame();
         }
        System.out.println("End of round " + _gameobj.whichRound);
     }
 
     //this is to make sure that double-clicks dont' result in lost points
     //with no actual updates
     protected long _lastUpdate = System.currentTimeMillis();
   
   /** Severside knowledge of numSpikes for any tiles created pre-game */
   protected int numSpikes = 4;  //default = 8
   
   protected int maxRounds = -1;
   
   /**Our puzzle board generator. */
   protected CBBoardGen _boardgen = new CBBoardGen();
   
   /** Handles our turn based game flow. */
   protected TurnGameManagerDelegate _turndel;

    /** Our game object. */
    protected CBObject _gameobj;
    
    /** Our game configuration. */
    protected ToyBoxGameConfig _gameconf;
}
